package com.cyx.spring.context;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.cyx.spring.annotation.*;
import com.cyx.spring.expc.BeanDefinitionException;
import com.cyx.spring.io.PropertyResolver;
import com.cyx.spring.io.ResourceResolver;
import com.cyx.spring.utils.ClassUtils;
import jakarta.annotation.Nullable;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.*;
import java.util.*;

@Slf4j
public class AnnotationConfigApplicationContext implements ConfigurableApplicationContext{

    Map<String, BeanDefinition> beans;

    PropertyResolver propertyResolver;

    List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();


    // 正在创建
    private Set<String> creatingBeanNames = new HashSet<>();

    public AnnotationConfigApplicationContext(Class<?> configClass, PropertyResolver propertyResolver) {
        this.propertyResolver = propertyResolver;
        Set<String> beanNames = scanBeans(configClass);
        ApplicationContextUtils.setConfigurableApplicationContext(this);
        createBeanDefinitions(beanNames);
        createBean();
        //依赖注入
        // 通过字段和set方法注入依赖:
        this.beans.values().forEach(this::injectBean);
        // 调用init方法:
        this.beans.values().forEach(this::initBean);

    }

    private void injectBean(BeanDefinition def) {
        Object instance = def.getInstance();
        Class<?> beanClass = def.getBeanClass();
        //如果被代理了，则获取原始类
        instance = getOriginBean(def);
        //注入字段
        injectProperties(def,beanClass,instance);
    }

    private Object getOriginBean(BeanDefinition def) {
        Collections.reverse(this.beanPostProcessorList);
        Object obj = def.getInstance();
        for (BeanPostProcessor beanPostProcessor : this.beanPostProcessorList) {
            Object o = beanPostProcessor.postProcessOnSetProperty(def.getInstance(), def.getName());
            if (o != obj){
                obj = o;
            }
        }
        return obj;


    }


    // 在当前类及父类进行字段和方法注入:
    void injectProperties(BeanDefinition def, Class<?> clazz, Object bean) {
        // 在当前类查找Field和Method并注入:
        for (Field f : clazz.getDeclaredFields()) {
            tryInjectProperties(def, clazz, bean, f);
        }
        for (Method m : clazz.getDeclaredMethods()) {
            tryInjectProperties(def, clazz, bean, m);
        }
        // 在父类查找Field和Method并注入:
        Class<?> superClazz = clazz.getSuperclass();
        if (superClazz != null) {
            // 递归调用:
            injectProperties(def, superClazz, bean);
        }
    }

    private void tryInjectProperties(BeanDefinition def, Class<?> clazz, Object bean, AccessibleObject a) {
        if (a instanceof Method){
            injectPropertiesForSet(def,clazz,bean,((Method) a));
        }
        if (a instanceof Field){
            injectPropertiesForFiled(def,clazz,bean,((Field) a));
        }

    }

    private void injectPropertiesForFiled(BeanDefinition def, Class<?> clazz, Object bean, Field a) {
        //字段注入
        Autowired autowired = a.getAnnotation(Autowired.class);
        if (autowired != null){
            String name = autowired.name();
            if (StrUtil.isNotBlank(name)){
                BeanDefinition dependOnBean = findBeanDefinition(name);
                if (dependOnBean != null){
                    ReflectUtil.setFieldValue(bean,a,dependOnBean.getInstance());
                } else if (autowired.value()) {
                    throw new RuntimeException("create bean "+def.getName()+"require bean" + name);
                }
            }else {
                //类型注入
                BeanDefinition dependOnBean = findBeanDefinition(a.getType());
                if (dependOnBean != null){
                    ReflectUtil.setFieldValue(bean,a,dependOnBean.getInstance());
                } else if (autowired.value()) {
                    throw new RuntimeException("create bean "+def.getName()+"require bean" + a.getType());
                }
            }
            return;
        }
        //value
        Value value = a.getAnnotation(Value.class);
        if (value != null){
            String value1 = value.value();
            Object filedValue = propertyResolver.getProperty(value1, a.getType());
            if (filedValue == null){
                throw new RuntimeException("create bean "+def.getName()+"require filed value" + a.getType());
            }
            ReflectUtil.setFieldValue(bean,a,filedValue);
        }
    }

    private void injectPropertiesForSet(BeanDefinition def, Class<?> clazz, Object bean, Method setter) {
        Autowired autowired = setter.getAnnotation(Autowired.class);
        if (autowired != null){
            Parameter[] parameters = setter.getParameters();
            if (parameters.length != 1){
                throw new RuntimeException("the setter method must have one parameter:"+setter.getName());
            }
            Parameter parameter = parameters[0];
            String name = autowired.name();
            Class<?> type = parameter.getType();
            BeanDefinition beanDefinition = findBeanDefinition(type, name);
            if (beanDefinition != null){
                ReflectUtil.invoke(bean,setter,beanDefinition.getInstance());
            } else if (autowired.value()) {
                throw new RuntimeException("create bean "+def.getName()+"require bean" + name);
            }
        }
    }

    private void initBean(BeanDefinition def) {
        Method initMethod = def.getInitMethod();
        if (initMethod != null){
            try {
                initMethod.invoke(def.getInstance());
            } catch (Exception e) {
                throw new RuntimeException("invoke init method error in bean:"+ def.getInitMethod(),e);
            }
        }
    }

    /**
     * 创建bean
     */
    private void createBean() {
        //首先创建@Configuration类型的Bean:
        List<BeanDefinition> configurationBeanList = this.beans.values()
                .stream()
                .filter(BeanDefinition::isConfiguration)
                .sorted(Comparator.comparingInt(BeanDefinition::getOrder))
                .toList();

        if (CollectionUtil.isNotEmpty(configurationBeanList)){
            for (BeanDefinition def : configurationBeanList) {
                createBeanAsEarlySingleton(def);
            }
        }
        //全部的postBeanProcessor
        List<BeanDefinition> postBeanProcessorBeanList = this.beans.values()
                .stream()
                .filter(BeanDefinition::isPostBeanProcessor)
                .sorted(Comparator.comparingInt(BeanDefinition::getOrder))
                .toList();
        List<BeanPostProcessor> beanPostProcessors = new ArrayList<>();
        for (BeanDefinition beanDefinition : postBeanProcessorBeanList) {
            Object beanPostProcessor = createBeanAsEarlySingleton(beanDefinition);
            beanPostProcessors.add((BeanPostProcessor) beanPostProcessor);
        }
        this.beanPostProcessorList.addAll(beanPostProcessors);

        //然后创建普通的bean
        List<BeanDefinition> componentBean = this.beans.values().stream()
                .filter(e -> e.getInstance() == null)
                .sorted(Comparator.comparingInt(BeanDefinition::getOrder))
                .toList();

        for (BeanDefinition beanDefinition : componentBean) {
            createBeanAsEarlySingleton(beanDefinition);
        }



    }

    /**
     * createBean
     * @param def
     * @return
     */
    public Object createBeanAsEarlySingleton(BeanDefinition def) {
        //首先判断循环依赖
        if (!creatingBeanNames.add(def.getName())) {
            throw new RuntimeException("Circular bean dependencies :" + def.getName());
        }
        log.debug("create Bean:{}",def.getName());
        Executable createMethod = null;
        //然后判断用构造方法还是工厂方法
        if (def.getFactoryMethod() != null) {
            createMethod = def.getFactoryMethod();
        }else {
            createMethod = def.getConstructor();
        }
        //获取参数
        Parameter[] parameters = createMethod.getParameters();
        Object[] args = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            //参数类型
            Parameter parameter = parameters[i];
            Class<?> type = parameter.getType();
            //是否有value autowird注解
            Autowired autowired = parameter.getAnnotation(Autowired.class);
            Value value = parameter.getAnnotation(Value.class);
            if (autowired != null){
                String name = autowired.name();
                BeanDefinition dependOn = findBeanDefinition(type,name);
                if (autowired.value() && dependOn == null){
                    throw new RuntimeException("crate bean:"+def.getName()+" is miss BeanType:" + type);
                }
                Object dependOnInstance = dependOn.getInstance();
                if (dependOnInstance == null){
                    dependOnInstance = createBeanAsEarlySingleton(dependOn);
                }
                args[i] = dependOnInstance;
            }else {
                //value 注解
                Object dependOnInstance = propertyResolver.getProperty(value.value(), type);
                args[i] = dependOnInstance;
            }

        }

        //反射执行即可
        createMethod.setAccessible(true);
        Object instance = createInstance(def,createMethod,parameters);
        //beanPostProcess
        for (BeanPostProcessor beanPostProcessor : this.beanPostProcessorList) {
            Object obj = beanPostProcessor.postProcessBeforeInitialization(instance, def.getName());
            if (instance != obj){
                instance = obj;
            }
        }
        def.setInstance(instance);
        this.creatingBeanNames.remove(def.getName());
        return instance;
    }

    private Object createInstance(BeanDefinition def, Executable createMethod, Parameter[] parameters) {
        if (createMethod instanceof Constructor<?>){
            try {
                Object[] params = findInstancesByParameters(parameters);
                return ((Constructor<?>) createMethod).newInstance(params);
            } catch (Exception e) {
                throw new RuntimeException("create instance error:",e);
            }
        }
        if (createMethod instanceof Method){
            String factoryName = def.getFactoryName();
            int lastIndex = factoryName.lastIndexOf(".");
            factoryName = StrUtil.lowerFirst(factoryName.substring(lastIndex+1));
            BeanDefinition beanDefinition = this.findBeanDefinition(factoryName);
            Object factoryInstance = beanDefinition.getInstance();
            try {
                Object[] params = findInstancesByParameters(parameters);
                return ((Method) createMethod).invoke(factoryInstance, params);
            } catch (Exception e) {
                throw new RuntimeException("create instance error:",e);
            }
        }

        throw new RuntimeException("create instance error:" + def);
    }

    private Object[] findInstancesByParameters(Parameter[] parameters) {
        Object[] args = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            Class<?> type = parameters[i].getType();
            Autowired autowired = parameters[i].getAnnotation(Autowired.class);
            if (autowired != null){
                BeanDefinition beanDefinition = findBeanDefinition(type);
                if (beanDefinition != null){
                    Object instance = beanDefinition.getInstance();
                    if (instance == null){
                        args[i] = createBeanAsEarlySingleton(beanDefinition);
                    }else {
                        args[i] = instance;
                    }
                } else if (autowired.value()) {
                    throw new RuntimeException("create Baen error...:");
                }
            }
            //value
            Value value = parameters[i].getAnnotation(Value.class);
            if (value != null){
                String key = value.value();
                args[i] = this.propertyResolver.getProperty(key,type);
            }

        }
        return args;

    }

    /**
     * find bean by type and name
     * @param type 类型
     * @param name 名称
     * @return
     */
    private BeanDefinition findBeanDefinition(Class<?> type, String name) {
        if (StrUtil.isNotBlank(name)){
            return findBeanDefinition(name);
        }
        return findBeanDefinition(type);

    }


    /**
     * find bean by name
     * @param name
     * @return
     */
    public BeanDefinition findBeanDefinition(String name){
        return beans.get(name);
    }

    @Nullable
    @Override
    public BeanDefinition findBeanDefinition(String name, Class<?> requiredType) {
        BeanDefinition beanDefinition = findBeanDefinition(name);
        assert beanDefinition != null;
        if (beanDefinition.getBeanClass().isAssignableFrom(requiredType)){
            return beanDefinition;
        }
        return null;
    }


    /**
     * find  bean by class
     * @param clazz
     * @return
     */
    public List<BeanDefinition> findBeanDefinitions(Class<?> clazz){
        return beans.values().stream().filter(beanDefinition -> beanDefinition.getBeanClass().equals(clazz))
                .sorted(Comparator.comparingInt(BeanDefinition::getOrder)).toList();
    }


    public BeanDefinition findBeanDefinition(Class<?> clazz){
        List<BeanDefinition> defs = findBeanDefinitions(clazz);
        if (defs.isEmpty()) { // 没有找到任何BeanDefinition
            return null;
        }
        if (defs.size() == 1) { // 找到唯一一个
            return defs.get(0);
        }
        // 多于一个时，查找@Primary:
        List<BeanDefinition> primaryDefs = defs.stream().filter(BeanDefinition::isPrimary).toList();
        if (primaryDefs.size() == 1) { // @Primary唯一
            return primaryDefs.get(0);
        }
        if (primaryDefs.isEmpty()) { // 不存在@Primary
           throw new RuntimeException(String.format("Multiple bean with type '%s' found, but no @Primary specified.", clazz.getName()));
        } else {
            // @Primary不唯一
            throw new RuntimeException(String.format("Multiple bean with type '%s' found, and multiple @Primary specified.", clazz.getName()));
            // n(String.format("Multiple bean with type '%s' found, and multiple @Primary specified.", type.getName()));
        }

    }




    private void createBeanDefinitions(Set<String> beanNames) {
        if (CollectionUtil.isEmpty(beanNames)) {
            beans =  MapUtil.empty();
        }
        beans = new HashMap<>();
        for (String beanName : beanNames) {
            Class<?> aClass = null;
            try {
                aClass = Class.forName(beanName);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException("not find the Class: " + beanName);
            }
            if (aClass.isAnnotation()){
                continue;
            }
            if (ClassUtil.isAbstract(aClass)){
                continue;
            }
            if (ClassUtil.isInterface(aClass)){
                continue;
            }
            Component component = ClassUtils.findAnnotation(aClass, Component.class);
            if (component == null) {
                continue;
            }
            BeanDefinition beanDefinition = createComponentBeanDefinition(aClass);
            log.debug("created beanDefinition: {}", beanDefinition.getName());
            addBeanDefinition(beanDefinition);
            //是否是配置类
            if (ClassUtils.findAnnotation(aClass, Configuration.class) != null) {
                Method[] declaredMethods = aClass.getDeclaredMethods();
                for (Method declaredMethod : declaredMethods) {
                    boolean isBean = declaredMethod.isAnnotationPresent(Bean.class);
                    if (isBean) {
                        BeanDefinition beanDefinitionHash = createFactoryBeanDefinition(aClass, declaredMethod);
                        log.debug("created beanDefinition: {}", beanDefinitionHash.getName());
                        addBeanDefinition(beanDefinitionHash);
                    }
                }
            }
        }

    }

    /**
     * Get public constructor or non-public constructor as fallback.
     */
    Constructor<?> getSuitableConstructor(Class<?> clazz) {
        Constructor<?>[] cons = clazz.getConstructors();
        if (cons.length == 0) {
            cons = clazz.getDeclaredConstructors();
            if (cons.length != 1) {
                throw new BeanDefinitionException("More than one constructor found in class " + clazz.getName() + ".");
            }
        }
        if (cons.length != 1) {
            throw new BeanDefinitionException("More than one public constructor found in class " + clazz.getName() + ".");
        }
        return cons[0];
    }


    private BeanDefinition createFactoryBeanDefinition(Class<?> aClass, Method declaredMethod) {

        Bean bean = declaredMethod.getAnnotation(Bean.class);
        if (bean != null) {
            BeanDefinition beanDefinition = new BeanDefinition();
            String value = bean.value();
            String name = null;
            if (StrUtil.isNotBlank(value)) {
                name = value;
            } else {
                name = declaredMethod.getName();
            }
            beanDefinition.setName(name);
            Class<?> returnType = declaredMethod.getReturnType();
            beanDefinition.setBeanClass(returnType);
            beanDefinition.setInstance(null);
            //beanDefinition.setConstructor(getSuitableConstructor(returnType));
            Configuration configuration = ClassUtils.findAnnotation(aClass, Configuration.class);
            String factoryName = null;
            if (configuration == null){
                throw new RuntimeException("create bean:"+name + "is error,the factory is miss,"+ aClass.getName());
            }else {
                factoryName = configuration.value();
                if (StrUtil.isBlank(factoryName)){
                    factoryName = StrUtil.lowerFirst(aClass.getSimpleName());
                }
            }

            beanDefinition.setFactoryName(aClass.getName());
            beanDefinition.setFactoryMethod(declaredMethod);
            Order order = declaredMethod.getAnnotation(Order.class);
            if (order != null) {
                beanDefinition.setOrder(order.value());
            }
            beanDefinition.setPrimary(declaredMethod.isAnnotationPresent(Primary.class));
            beanDefinition.setInitMethodName(bean.initMethod());
            beanDefinition.setDestroyMethodName(bean.destroyMethod());
            beanDefinition.setInitMethod(ReflectUtil.getMethod(returnType, bean.initMethod()));
            beanDefinition.setDestroyMethod(ReflectUtil.getMethod(returnType, bean.destroyMethod()));
            return beanDefinition;
        }
        return null;


    }

    private void addBeanDefinition(BeanDefinition beanDefinition) {
        if (beanDefinition != null) {
            Object obj = this.beans.put(beanDefinition.getName(), beanDefinition);
            if (obj != null) {
                throw new RuntimeException("the beanName is already exist: " + beanDefinition.getName());
            }

        }
    }

    private BeanDefinition createComponentBeanDefinition(Class<?> aClass) {
        BeanDefinition beanDefinition = new BeanDefinition();
        beanDefinition.setName(ClassUtils.getBeanName(aClass));
        beanDefinition.setBeanClass(aClass);
        beanDefinition.setInstance(null);
        beanDefinition.setConstructor(getSuitableConstructor(aClass));
        beanDefinition.setFactoryName(null);
        beanDefinition.setFactoryMethod(null);
        beanDefinition.setOrder(getOrder(aClass));
        beanDefinition.setPrimary(aClass.isAnnotationPresent(Primary.class));
        Method initMethod = getInitMethod(aClass);
        if (initMethod != null) {
            beanDefinition.setInitMethodName(initMethod.getName());
            beanDefinition.setInitMethod(initMethod);
        }
        Method destroyMethod = getDestroyMethod(aClass);
        if (destroyMethod != null) {
            beanDefinition.setDestroyMethodName(destroyMethod.getName());
            beanDefinition.setDestroyMethod(destroyMethod);
        }
        return beanDefinition;

    }

    private int getOrder(Class<?> clazz) {
        if (clazz != null) {
            Order annotation = ClassUtils.findAnnotation(clazz, Order.class);
            if (annotation != null) {
                return annotation.value();
            }
        }
        return Integer.MAX_VALUE;
    }

    private Method getInitMethod(Class<?> clazz) {
        return ClassUtils.findAnnotationMethod(clazz, PostConstruct.class);
    }

    private Method getDestroyMethod(Class<?> clazz) {
        return ClassUtils.findAnnotationMethod(clazz, PreDestroy.class);
    }


    /**
     * 扫描配置类中的所有bean
     *
     * @param configClass
     * @return
     */
    private Set<String> scanBeans(Class<?> configClass) {
        //获取需要扫描的包
        ComponentScan componentScan = ClassUtils.findAnnotation(configClass, ComponentScan.class);
        Set<String> res = new HashSet<>();
        String[] basePackages;
        if (componentScan != null) {
            basePackages = componentScan.value();
        } else {
            //获取当前包的路径
            basePackages = new String[]{configClass.getPackage().getName()};
        }
        for (String basePackage : basePackages) {
            List<String> scan = new ResourceResolver(basePackage)
                    .scan(resource -> resource.name().replace(".class", "").replace("/", ".").replace("\\","."));
            res.addAll(scan);
        }
        res.add(configClass.getName());
        //扫描import
        Import anImport = ClassUtils.findAnnotation(configClass, Import.class);
        if (anImport != null) {
            Class<?>[] value = anImport.value();
            for (Class<?> aClass : value) {
                res.add(aClass.getName());
            }
        }
        return res;
    }


    @Override
    public boolean containsBean(String name) {
        return this.beans.containsKey(name);
    }

    @Override
    public <T> T getBean(String name) {
        return (T) this.findBeanDefinition(name).getInstance();
    }

    @Override
    public <T> T getBean(String name, Class<T> requiredType) {
        return ((T) this.findBeanDefinition(name, requiredType).getInstance());
    }

    @Override
    public <T> T getBean(Class<T> requiredType) {
        return (T) this.findBeanDefinition(requiredType).getInstance();
    }

    @Override
    public <T> List<T> getBeans(Class<T> requiredType) {
        List<T> ts = new ArrayList<>();
        List<BeanDefinition> beanDefinitions = this.findBeanDefinitions(requiredType);
        if (CollectionUtil.isNotEmpty(beanDefinitions)){
            for (BeanDefinition beanDefinition : beanDefinitions) {
                ts.add((T) beanDefinition.getInstance());
            }
        }
        return ts;
    }

    @Override
    public void close() {
        //执行bean的销毁方法
        this.beans.values().forEach(this::closeMethod);
    }

    private void closeMethod(BeanDefinition beanDefinition) {
        if (beanDefinition != null){
            Method destroyMethod = beanDefinition.getDestroyMethod();
            if (destroyMethod == null){
                return;
            }
            try {
                destroyMethod.invoke(beanDefinition.getInstance());
            } catch (Exception e) {
                throw new RuntimeException("执行销毁方法报错..."+destroyMethod,e);
            }
        }
    }
}